/*
 * kjgetopt.c
 * KJ's simplified stream archiver (kjssar)
 * 
 * (C)Copyright 2013-2015 by Klaus-J. Wolf, kj (at) seismic (dot) de
 * This file is licensed under the General Public License Version 2.
 * http://www.gnu.org/licenses/gpl-2.0.html
 * 
 * This is a proof-of-concept implementation.
 * 
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "kjgetopt.h"


const char *kjparseopt_errmsg;
int kjparseopt_errnum;
int kjparseopt_terminate_on_parm = 0;


static int strchix(const char *s, int c)
{
    int i;
    
    for(i=0; s[i]; i++)
        if(s[i]==c)
            return i;
    return -1;
}


static struct CLOConfig *getnode(int id, int addup, struct CLOConfig **root, struct CLOConfig **lastp)
{
    struct CLOConfig *p;
    
    if(!addup)
        /* find old node */
        for(p=(*root); p; p=p->nxt)
            if(p->id==id)
                return p;
    
    /* make new node */
    if((*root)==NULL)
        (*root)=(*lastp)=malloc(sizeof(struct CLOConfig));
    else
        (*lastp)=(*lastp)->nxt=malloc(sizeof(struct CLOConfig));
    if((*lastp)==NULL)
    {
        kjparseopt_errmsg="out of memory";
        kjparseopt_errnum=0;
        return NULL;
    }
    memset(*lastp, 0, sizeof(struct CLOConfig));
    (*lastp)->id = id;
    return *lastp;
}


cloconfig_t kjparseopts_f(int argc, char * const argv[], struct CLOption *opts, int optslen, void (*cb_doparm)(cloconfig_t,const char *), int *newargp)
{
    struct CLOConfig *res=NULL, *rp, *tmpp;
    int i,j,k,p,ok,parmflag;
    
    kjparseopt_errmsg=NULL;   /* a return value of NULL might be not an error */
    kjparseopt_errnum=-1;
    
    for(i=1; i<argc; i++)
    {
        ok=0;
        
        if(argv[i][0]=='-')
        {
            if(argv[i][1]=='-')
            {
                if(argv[i][2]==0)    /* end of options */
                {
                    if(newargp)
                        *newargp = i+1;
                    else
                        if(cb_doparm)
                        {
                            for(++i; i<argc; i++)
                                cb_doparm(res, argv[i]);
                        }
                    return res;
                }
                /* else: long option */
                
                p = strchix(argv[i], '=');
                /* parameter is appended to the long option? */
                if(p>=0)
                {
                    for(j=0; j<optslen; j++)
                        if(opts[j].longoption && !strncmp(argv[i]+2, opts[j].longoption, p-2) && opts[j].longoption[p-2]==0)
                        {
                            tmpp=getnode(opts[j].id, opts[j].addup, &res, &rp);
                            if(!tmpp)
                                return NULL;
                            /* opts[j].parmflag is ignored here! */
                            tmpp->flag++;
                            tmpp->val = strdup(argv[i]+p+1);
                            ok=1;
                            break;
                        }
                }
                else
                {
                    for(j=0; j<optslen; j++)
                        if(opts[j].longoption && !strcmp(argv[i]+2, opts[j].longoption))
                        {
                            tmpp=getnode(opts[j].id, opts[j].addup, &res, &rp);
                            if(!tmpp)
                                return NULL;
                            tmpp->flag++;
                            if(opts[j].parmflag)
                            {
                                if(++i>=argc)
                                {
                                    kjparseopt_errmsg="missing parameter";
                                    kjparseopt_errnum=2;
                                    return NULL;
                                }
                                tmpp->flag++;
                                tmpp->val = strdup(argv[i]);
                                tmpp->multi = opts[j].addup;
                            }
                            ok=1;
                            break;
                        }
                }
            }
            else
            {
                /* short option(s) */
                for(k=1; argv[i][k]; k++)
                {
                    parmflag=0;
                    for(j=0; j<optslen; j++)
                        if(opts[j].shortoption && argv[i][k]==opts[j].shortoption)
                        {
                            tmpp=getnode(opts[j].id, opts[j].addup, &res, &rp);
                            if(!tmpp)
                                return NULL;
                            tmpp->flag++;
                            parmflag=opts[j].parmflag;
                            if(parmflag)
                            {
                                if(argv[i][k+1])
                                {
                                    tmpp->val = strdup(argv[i]+k+1);
                                    tmpp->multi = opts[j].addup;
                                }
                                else
                                {
                                    if(++i>=argc)
                                    {
                                        kjparseopt_errmsg="missing parameter";
                                        kjparseopt_errnum=2;
                                        return NULL;
                                    }
                                    tmpp->val = strdup(argv[i]);
                                    tmpp->multi = opts[j].addup;
                                }
                            }
                            ok=1;
                            break;
                        }
                    if(parmflag)
                        break;
                }
            }
        }
        else
        {
            /* parameter */
            if(cb_doparm)
            {
                cb_doparm(res, argv[i]);
            }
            else
                if(kjparseopt_terminate_on_parm)
                {
                    if(newargp)
                        *newargp = i;
                    return res;
                }
            /* else: ignore parameter */
            ok=1;
        }
        if(!ok)
        {
            kjparseopt_errmsg="unrecognized option";
            kjparseopt_errnum=1;
            return NULL;
        }
    }
    return res;
}


int kjgetoptflag(cloconfig_t cfg, int id)
{
    for(; cfg; cfg=cfg->nxt)
    {
        if(cfg->id==id)
            return cfg->flag;
    }
    return 0;
}


const char *kjgetoptparm(cloconfig_t cfg, int id, char sep)
{
    int n=0;
    char *res;
    int i;
    
    for(; cfg; cfg=cfg->nxt)
    {
        if(cfg->id==id && cfg->val)
        {
            if(!sep || !cfg-multi)
                return strdup(cfg->val);
            n+=strlen(cfg->val)+1;
        }
    }
    if(!n++)
        return NULL;
    
    res = malloc(n+1);
    if(!res)
        return NULL;
    res[0]=0;
    
    for(; cfg; cfg=cfg->nxt)
    {
        if(cfg->id==id && cfg->val)
        {
            strcat(res, cfg->val);
            i=strlen(res);
            res[i++]=sep;
            res[i++]=0;
        }
    }
    return res;
}


void kjdestroykjopts(cloconfig_t cfg)
{
    struct CLOConfig *nxt;
    for(; cfg; cfg=nxt)
    {
        nxt=cfg->nxt;
        if(cfg->val)
            free(cfg->val);
        free(cfg);
    }
}



void kjdisplayhelp(struct CLOption *opts, int optslen, FILE *f)
{
    int i;
    int pos;
    
    for(i=0; i<optslen; i++)
    {
        pos=0;
        pos+=fprintf(f, "   ");
        if(opts[i].shortoption)
        {
            pos+=fprintf(f, "-%c", opts[i].shortoption);
            if(opts[i].longoption)
                pos+=fprintf(f, ", ");
            else
                if(opts[i].parmflag)
                    pos+=fprintf(f, " <parm>");
        }
        if(opts[i].longoption)
        {
            pos+=fprintf(f, "--%s", opts[i].longoption);
            if(opts[i].parmflag)
                pos+=fprintf(f, "=<parm>");
        }
        for(; pos<40; pos++)
            fputc(' ', f);
        fputs(opts[i].explanation, f);
        fputc('\n', f);
    }
    
}

